#-*- coding: utf-8 -*-
#
# Copyright (C) 2006-2007 Ville Leskinen
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

"""This module provides image handling classes"""

import glob
import Image
import os

from errors import *
from listener import *

_fileExts = ('bmp', 'dib', 'BMP', 'DIB',		# bmp
     'gif', 'GIF',					# gif
     'jpg', 'jpe', 'jpeg', 'JPG', 'JPE', 'JPEG',	# jpeg
     'png', 'PNG')					# png


class ImageBox(object):
	"""Class for managing image sizes. This class has variables width, height,
	longSide and shortSide. The class is then able to calculate image size that
	is able to fit the box.	Setting variable to zero means there will be no
	restrictions to that direction. If you have set width or height, longSide 
	and shortSide will have no effect."""
	def __init__(self, width=0, height=0, longSide=0, shortSide=0):
		"""Constructor. Using negative size values or setting shortSide
		longer than longSide raises L{igCreator.errors.OptionError}.
		
		@param width: width of the box
		@type width: integer
		@param height: height of the box
		@type height: integer
		@param longSide: longer side of the box
		@type longSide: integer
		@param shortSide: shorter side of the box
		@type shortSide: integer"""
		if width < 0 or height < 0 or longSide < 0 or shortSide < 0:
			raise OptionError, "Negative size value"

		if shortSide > longSide and longSide != 0:
			raise OptionError, "shortSide should be shorter than longSide"

		self.width = width
		self.height = height
		self.longSide = longSide
		self.shortSide = shortSide

	def parseString(self, string):
		"""
		Parses string that is in format <option>=<int> or <width>x<height>. If string
		is none does nothing.

		@param string: string that will be parsed
		@type string: string
		"""
		if string:
			sizeOpt = string.split('=')
			if len(sizeOpt) == 1:
				sizeOpt = string.split('x')
				if len(sizeOpt) == 2:
					self.width = int(sizeOpt[0])
					self.height = int(sizeOpt[1])
				else:
					raise OptionError, "Invalid size option string: %s" % (string)

			else:
				(sizeType, size) = sizeOpt
				if sizeType == 'width':
					self.width = int(size)
				elif sizeType == 'height':
					self.height = int(size)
				elif sizeType == 'long':
					self.longSide = int(size)
				elif sizeType == 'short':
					self.shortSide = int(size)

				else:
					raise OptionError, "Invalid size option string: %s" % (string)


			
	def fit(self, image):
		"""Fits the given picture to the box.

		@param image: image that's going to be resized
		@type image: Image.image
		@returns: new dimensions in format (width, height)"""
		(oldWidth, oldHeight) = image.size
		aspect = float(oldWidth)/float(oldHeight)

		newWidth = 0
		newHeight = 0

		if self.width == 0 and self.height == 0 and self.longSide == 0 and self.shortSide == 0:
			return (oldWidth, oldHeight)

		if self.width != 0 or self.height != 0:
			if self.width == 0:
				newHeight = self.height
				newWidth = int(newHeight * aspect)
			elif self.height == 0:
				newWidth = self.width
				newHeight = int(newWidth/aspect)
			else:
				boxAspect = float(self.width)/float(self.height)
				if aspect > boxAspect:
					# width is controlling
					newWidth = self.width
					newHeight = int(newWidth/aspect)
				else:
					newHeight = self.height
					newWidth = int(newHeight * aspect)
		else:
			if self.longSide == 0:
				if oldHeight < oldWidth:
					newHeight = self.shortSide
					newWidth = int(newHeight * aspect)
				else:
					newWidth = self.shortSide
					newHeight = int(newWidth/aspect)

			elif self.shortSide == 0:
				if oldHeight > oldWidth:
					newHeight = self.longSide
					newWidth = int(newHeight * aspect)
				else:
					newWidth = self.longSide
					newHeight = int(newWidth/aspect)
			
			else:
				boxAspect = float(self.longSide)/float(self.shortSide)

				if aspect > 1:
					if boxAspect < aspect:
						newWidth = self.longSide
						newHeight = int(newWidth/aspect)
					else:
						newHeight = self.shortSide
						newWidth = int(newHeight * aspect)

				else:
					turnedAspect = 1.0 / aspect
					if turnedAspect < aspect:
						newHeight = self.longSide
						newWidth = int(newHeight/turnedAspect)
					else:
						newWidth = self.shortSide
						newHeight = int(newWidth * turnedAspect)


		return (newWidth, newHeight)



class ImageListOptions(object):
	"""Class for ImageList options"""
	def __init__(self):
		self.thumbBox = ImageBox()
		self.picBox = ImageBox()
		self.setResizeFilter('antialias')

	def setThumbBox(self, thumbBox):
		"""Sets the thumbnail box

		@param thumbBox: the thumbnail box
		@type thumbBox: L{igCreator.imagelist.ImageBox}"""
		self.thumbBox = thumbBox

	def setPicBox(self, picBox):
		"""Sets the picture box

		@param picBox: the picture box
		@type picBox: L{igCreator.imagelist.ImageBox}"""
		self.picBox = picBox
	
	def setResizeFilter(self, resizeFilter):
		"""Sets resize filter for images.

		@param resizeFilter: filter name
		@type resizeFilter: string"""
		if resizeFilter == 'nearest':
			self.resizeFilter = Image.NEAREST
		elif resizeFilter == 'bilinear':
			self.resizeFilter = Image.BILINEAR
		elif resizeFilter == 'bicubic':
			self.resizeFilter = Image.BICUBIC
		elif resizeFilter == 'antialias':
			self.resizeFilter = Image.ANTIALIAS
		else:
			raise OptionError, "Invalid filter: %s" % (resizeFilter)

class ImageList(object):
	"""Class for handling images"""

	def __init__(self, images = []):
		"""
		Constuctor

		@param images: list of image filenames
		@type images: list
		"""
		self.images = images
		self._checkImages()
		self.picList = []
		self.thumbList = []
		self.options = None
		self.verbose = True

	def _checkImages(self):
		for image in self.images:
			(root, ext) = os.path.splitext(image)
			ext = ext.strip('.')
			if ext not in _fileExts:
				raise ImageListError, "Not supported image file: %s" % (image)
			
		return True


	def setOptions(self, options):
		"""Set options for imagelist

		@param options: the options
		@type options: L{igCreator.imagelist.ImageListOptions}"""
		self.options = options

	def readListFromPath(self, path):
		"""Creates the imagelist from images in given path

		@param path: the path
		@type path: string
		"""

		if not os.path.isdir(path):
			raise ImageListError, "directory " + path + " not valid"
		
		tmpList = []
		for ext in _fileExts:
			tmpList.extend(glob.glob(path + '/*.' + ext))

		if not len(tmpList):
			raise ImageListError, "no supported images found in " + os.path.abspath(path)

		tmpList.sort()
		self.images = tmpList
	
	def _createThumb(self, image, path):
		"""Creates a single thumbnail from image.

		@param image: the image
		@type image: Image.image
		@param path: path were to write the image
		@type path: string"""
		(width, height) = self.options.thumbBox.fit(image)

		if self.verbose:
			print "Creating thumbnail: %s" % (path)
			print "Size: %dx%d" % (width, height)
		image.resize((width, height), self.options.resizeFilter).save(path)
			

	def _createPicture(self, image, path):
		"""Creates a single resized image from given image.

		@param image: the image
		@type image: Image.image
		@param path: path were to write the image
		@type path: string"""
		(oldWidth, oldHeight) = image.size
		(width, height) = self.options.picBox.fit(image)

		if self.verbose:
			print "Creating image: %s" % (path)
			print "Old size: %dx%d" % (oldWidth, oldHeight)
			print "New size: %dx%d" % (width, height)
		image.resize((width, height), self.options.resizeFilter).save(path)

	def create(self, picPath, thumbPath):
		"""Creates thumbnails and pictures to given paths.

		@param picPath: path where pictures will be created
		@type picPath: string
		@param thumbPath: path where thumbnails will be created
		@type thumbPath: string
		"""
	
		if not self.options:
			raise ImageListError, "no options for images"

		if not os.path.isdir(picPath):
			os.makedirs(picPath)

		if not os.path.isdir(thumbPath):
			os.makedirs(thumbPath)

		for imagePath in self.images:
			image = Image.open(imagePath)

			imageName = os.path.basename(imagePath)
			self._createPicture(image,os.path.join(picPath, imageName))
			self.picList.append(imageName)

			thumbName = 'thumb_' + os.path.basename(imagePath)
			self._createThumb(image, os.path.join(thumbPath, thumbName))
			self.thumbList.append(thumbName)

class ImageResizeEvent(ListenerEvent):
	def __init__(self, message=None):
		ListenerEvent.__init__(self, 10, message)

class ListenableImageList(ImageList, Listenable):
	"""ImageList that is able to call listeners."""

	def __init__(self, images=[]):
		ImageList.__init__(self, images)
		Listenable.__init__(self)
		self.verbose = False

	def create(self, picPath, thumbPath):

		for listener in self.listeners:
			for i in self.images:
				# two events for picture resize and thumbnail resize
				listener.addEvent(ImageResizeEvent())
				listener.addEvent(ImageResizeEvent())

		ImageList.create(self, picPath, thumbPath)

	def _createPicture(self, image, path):
		self._eventHappening(ImageResizeEvent("Resizing: %s" % (path)))
		ImageList._createPicture(self, image, path)

	def _createThumb(self, image, path):
		self._eventHappening(ImageResizeEvent("Generating thumbnail: %s" % (path)))
		ImageList._createThumb(self, image, path)
